/* Copyright  2009-2010
   @Author
   Richard Changde Yin            e-mail yinchangde@hotmail.com

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */


/**
 * @filename: allocator.h
 */
#ifndef __ALLOCATOR_H__
#define __ALLOCATOR_H__

#include <global.h>
#include <os.h>
#include <mutex.h>
#include <error.h>
#include<read-config.h>

typedef void Page;
extern char ChunkName[MAX_CHUNKS][CHUNK_NAME_LEN];

enum AllocType
{
    FixedSizeAllocator    = 0,
    VariableSizeAllocator = 1,
    UnknownAllocator
};

//Used to store the meta data information about the variable size data
class VarSizeInfo
{
public:
    VarSizeInfo(){}
    /// the size_ of data
    size_t size_;
    /// the flag
    int isUsed_;
};

//Each Page has this info.
//pages are of size PAGE_SIZE normally.
//If data size is more than PAGE_SIZE then
//continuous pages are merged and those pages wont
//have this info in them.Only the start page where that
//data is stored will have this info
//This object is stored at the start of each page
class PageInfo
{
public:
     int32_t isUsed_      ;
     int32_t hasFreeSpace_;
     bool    isMerged_    ;
     Page *nextPageAfterMerge_; //used only in case of
     //where pages are merged to store data which are more than
     //PAGE_SIZE.
     //More detail about how it is used is found in Database::getFreePage

     Page *nextPage_; //next page in the same chunk

     void setPageAsUsed(size_t offset);

     void setFirstPageAsUsed();
};

class Chunk;


//Iterator for the data
//Data is stored in chunks and this class gives
//iterator for it.
class ChunkIterator
{
    int64_t   chunkID_;
    size_t    allocSize_; // used if it is a fixed size allocator
    AllocType allocType_;

    //current iterating page
    PageInfo *iterPage_;

    //Each page is divided into nodes of size allocSize_
    //This gives the offset of the node in the page
    int   nodeOffset_;
    char* data;
    char* iterPageEnd;

    //Total number of nodes in the page
    //It is a constant value for this chunk
    //and it is cached for performance
    uint32_t    noOfNodes_;

    ChunkIterator& operator = (const ChunkIterator&);

public:
    ChunkIterator():chunkID_   (-1),
                    allocSize_ (0),
                    allocType_ (UnknownAllocator),
                    iterPage_  (0),
                    nodeOffset_(0),
                    data (0),
                    iterPageEnd(0),
                    noOfNodes_ (0)
                    {}
    ChunkIterator(const ChunkIterator& rhs)
    {
      chunkID_   = rhs.chunkID_;
      allocSize_ =rhs.allocSize_;
      allocType_ =rhs.allocType_;
      iterPage_  =rhs.iterPage_;
      nodeOffset_=rhs.noOfNodes_;
      data       = rhs.data;
      iterPageEnd=rhs.iterPageEnd;
      noOfNodes_ = rhs.noOfNodes_;
    }

    ChunkIterator(ChunkIterator& rhs)
    {
      chunkID_   = rhs.chunkID_;
      allocSize_ =rhs.allocSize_;
      allocType_ =rhs.allocType_;
      iterPage_  =rhs.iterPage_;
      nodeOffset_=rhs.noOfNodes_;
      data       = rhs.data;
      iterPageEnd=rhs.iterPageEnd;
      noOfNodes_ = rhs.noOfNodes_;
    }
    void* nextElement();
    friend class Chunk;

};

class Database;
class DatabaseManagerImpl;

class Chunk
{
    int64_t chunkID_;
    // used if it is a fixed size allocator
    size_t    allocSize_;

    AllocType allocType_;

    //Current page where the last data allocation was made
    Page *curPage_;

    //Page where data allocation was made for the first time
    //This is the start of the data
    //Iterator should start from this page
    Page *firstPage_;

    char chunkName[CHUNK_NAME_LEN];
    /// the mutex of the Chunk
    Mutex chunkMutex_;

    Chunk():chunkID_(-1){
      memset(chunkName,0, sizeof(chunkName));
      curPage_      = 0 ;
      firstPage_    = 0 ;
    }

public:

    //sets the size of the allocator
    //for fixed size allocator
    void setSize(size_t size);

    inline void setChunkNameForSystemDB(int id)
    {
        strcpy(chunkName,ChunkName[id]);
    }

    inline void setChunkName(char const *name){
       strcpy(chunkName,name);
    }

    inline void setChunkName (char const* name, const int _length)
    {
        /// TODO: _length might be larger than CHUNK_NAME_LEN
        ::memcpy(chunkName,name, _length) ;
         namelength             = _length ;
         chunkName[namelength]  = 0;
    }

    inline char *getChunkName(){return chunkName;}

    inline size_t getSize()                  { return allocSize_; }
    inline void setChunkID(int64_t id)  { chunkID_ = id; }
    inline int64_t getChunkID()                  { return chunkID_; }
    inline void setAllocType(AllocType type) { allocType_ = type; }
    inline AllocType getAllocType()          { return allocType_; }
    inline Page* getFirstPage(){ return firstPage_; }

    PageInfo* getPageInfo(Database *db, void *ptr);
    void* allocate(Database *db, BackEnd::DbRetVal *status = NULL);

    void* allocate(Database *db, size_t size, BackEnd::DbRetVal *status = NULL);

    void  free(Database *db, void* ptr);

    inline ChunkIterator getIterator()
    {
        ChunkIterator iter;
         if (!allocSize_) {
            return iter;
         }
         iter.chunkID_    = chunkID_;
         iter.allocSize_  = allocSize_;
         iter.allocType_  = allocType_;
         iter.iterPage_   =(PageInfo*) firstPage_;
         iter.nodeOffset_ = 0;
         iter.noOfNodes_ =(uint32_t) OS::floor((PAGE_SIZE - sizeof(PageInfo)) / allocSize_);
         iter.data = NULL ;
         iter.iterPageEnd = ((char*)firstPage_) +PAGE_SIZE;
         return iter;
    }

    void print();

    inline uint64_t getTotalDataNodes()
    {
        uint64_t totalNodes =0;

        if (0 == allocSize_) //->variable size allocator
        {
            Page *page = ((PageInfo*)firstPage_);
            while(NULL != page)
            {
                VarSizeInfo *varInfo=(VarSizeInfo*)(((char*)page)+sizeof(PageInfo));
                while ((char*) varInfo < ((char*)page + PAGE_SIZE))
                {
                  if (1 == varInfo->isUsed_) totalNodes++;
                  varInfo = (VarSizeInfo*)((char*)varInfo + sizeof(VarSizeInfo)
                                        +varInfo->size_);
                }
                page = ((PageInfo*) page)->nextPage_;
            }
            return totalNodes;
        }

        //TODO::for large size allocator
        if (allocSize_ >PAGE_SIZE)//->each page has only one data node
        {
            Page *page = ((PageInfo*)firstPage_);
            while(NULL != page)
            {
                    //current it page wise  later this will done
               if(1==*(int*)(((char*)page)+sizeof(PageInfo)))
                            totalNodes++;
               page = ((PageInfo*) page)->nextPage_;
            }
            return totalNodes;
        }

        int noOfDataNodes=OS::floor((PAGE_SIZE - sizeof(PageInfo))/allocSize_);
        PageInfo* pageInfo = ((PageInfo*)firstPage_);
        unsigned char *data = ((unsigned char*)firstPage_) + sizeof(PageInfo);
        int i=0;
        while( pageInfo != NULL )
        {
            data = ((unsigned char*)pageInfo) + sizeof(PageInfo);
            for (i = 0; i< noOfDataNodes; i++)
            {
                if (*((int*)data) == 1)  { totalNodes++;}
                data = data + allocSize_;
            }
            pageInfo = (PageInfo*)(((PageInfo*)pageInfo)->nextPage_) ;
        }
        return totalNodes;
    }

    size_t totalPages();

    int compact();

    inline int getNameLength () {  return  namelength ; }

    inline void init(){
      chunkID_=-1;
      memset(chunkName,0, sizeof(chunkName));
      curPage_ =firstPage_ = 0 ;
      allocSize_= 0;
      initMutex();
    }

    inline void reset(){
      memset(chunkName,0, sizeof(chunkName));
      chunkID_ = -1;
      allocSize_ = 0;
      curPage_ =firstPage_ = 0 ;
      destroyMutex();
    }
private:

    inline int initMutex()
    {
        return chunkMutex_.init((char*)"Chunk");
    }

    inline int getChunkMutex    (int procSlot)
    {
      return chunkMutex_.getLock(procSlot);
    }

    inline int releaseChunkMutex(int procSlot)
    {
      return chunkMutex_.releaseLock(procSlot);
    }

    inline int destroyMutex()
    {
        return chunkMutex_.destroy();
    }

    void createDataBucket(Page *page, size_t totalSize, size_t needSize);

    void splitDataBucket(VarSizeInfo *varInfo, size_t needSize);

    void* varSizeFirstFitAllocate(size_t size);

    void freeForLargeAllocator(void *ptr, int pslot);

    void freeForVarSizeAllocator(void *ptr, int pslot);

    void* allocateForLargeDataSize(Database *db);

    void* allocateFromFirstPage(Database *db, int noOfDataNodes);

    void* allocateFromNewPage(Database *db);

    void* allocateForLargeDataSize  (Database *db, size_t size);

    void* allocFromNewPageForVarSize(Database *db, size_t size);

    void* allocateFromCurPageForVarSize(size_t size);

    int  namelength ;

    friend class Database;
    friend class DatabaseManagerImpl;
};
#endif   //__ALLOCATOR_H__
